package com.youboy.oauth;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.youboy.oauth.common.OAuthException;
import com.youboy.oauth.consumer.InvalidConsumerKeyException;
import com.youboy.oauth.consumer.OAuthConsumerService;
import com.youboy.oauth.model.HttpOAuthRequest;
import com.youboy.oauth.model.OAuthConsumerAuthentication;
import com.youboy.oauth.model.OAuthRequest;
import com.youboy.oauth.nonce.InvalidNonceException;
import com.youboy.oauth.nonce.OAuthNonceService;
import com.youboy.oauth.provider.OAuthProvider;
import com.youboy.oauth.provider.OAuthProviderBuilder;
import com.youboy.oauth.signature.CoreOAuthSignatureFactory;
import com.youboy.oauth.signature.InvalidSignatureException;
import com.youboy.oauth.signature.OAuthSignatureFactory;
import com.youboy.oauth.token.InvalidTokenException;
import com.youboy.oauth.token.OAuthTokenService;
import com.youboy.oauth.verifier.OAuthTokenVerifierService;
import com.youboy.oauth.verifier.RandomOAuthTokenVerifierService;

/**
 * 
 * @author loudyn
 * 
 */
public abstract class OAuthProcessFilterSupport implements Filter {

	private OAuthConsumerService consumerService;
	private OAuthTokenService tokenService;
	private OAuthTokenVerifierService tokenVerifierService = new RandomOAuthTokenVerifierService();
	private OAuthNonceService nonceService;
	private OAuthSignatureFactory signatureFactory = new CoreOAuthSignatureFactory();

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse,
	 * javax.servlet.FilterChain)
	 */
	@Override
	public final void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

		OAuthRequest oauthRequest = null;
		OAuthConsumerAuthentication consumerAuthentication = null;
		try {

			oauthRequest = oauthRequestWasCreated((HttpServletRequest) request);
			OAuthProvider oauthProvider = new OAuthProviderBuilder().provide(oauthRequest)
																	.withOAuthConsumerService(getConsumerService())
																	.withOAuthTokenService(getTokenService())
																	.withOAuthNonceService(getNonceService())
																	.withOAuthSignatureFactory(getSignatureFactory())
																	.build();

			consumerAuthentication = oauthProvider.exceptOAuthRequest().exceptOAuthConsumer()
																		.exceptOAuthToken()
																		.exceptOAuthNonce()
																		.exceptOAuthSignature()
																		.returnOAuthConsumerAuthentication();

			validateOAuthAuthentication(consumerAuthentication);
			onValidOAuthAuthentication(consumerAuthentication, (HttpServletRequest) request, (HttpServletResponse) response, chain);

		} catch (Exception e) {
			if (e instanceof OAuthException) {
				onInvalidOAuthAuthentication((HttpServletRequest) request, (HttpServletResponse) response, (OAuthException)e);
				return;
			}

			if (e instanceof ServletException) {
				throw (ServletException) e;
			}

			throw new RuntimeException(e);
		} finally {
			afterOAuthRequestCompleted(oauthRequest, consumerAuthentication);
		}
	}

	/**
	 * 
	 * @param request
	 * @return
	 */
	protected OAuthRequest oauthRequestWasCreated(HttpServletRequest request) {
		return new HttpOAuthRequest(request);
	}

	/**
	 * @param consumerAuthentication
	 * @param oauthRequest
	 * 
	 */
	protected void afterOAuthRequestCompleted(OAuthRequest oauthRequest, OAuthConsumerAuthentication consumerAuthentication) {
	}

	/**
	 * 
	 * @param consumerAuthentication
	 */
	protected void validateOAuthAuthentication(OAuthConsumerAuthentication consumerAuthentication) throws Exception {
	}

	/**
	 * 
	 * @param consumerAuthentication
	 * @param request
	 * @param response
	 * @param chain
	 * @throws Exception
	 */
	protected abstract void onValidOAuthAuthentication(OAuthConsumerAuthentication consumerAuthentication,
													HttpServletRequest request,
													HttpServletResponse response,
													FilterChain chain) throws Exception;

	/**
	 * 
	 * @param request
	 * @param response
	 * @param e
	 * @throws IOException
	 */
	protected void onInvalidOAuthAuthentication(HttpServletRequest request, HttpServletResponse response, OAuthException e) throws IOException {

		if (e instanceof InvalidConsumerKeyException) {
			response.sendError(401, e.getMessage());
			return;
		}

		if (e instanceof InvalidTokenException) {
			response.sendError(401, e.getMessage());
			return;
		}

		if (e instanceof InvalidSignatureException) {
			response.sendError(401, e.getMessage());
			return;
		}

		if (e instanceof InvalidNonceException) {
			response.sendError(401, e.getMessage());
			return;
		}

		response.sendError(400, e.getMessage());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.servlet.Filter#destroy()
	 */
	@Override
	public void destroy() {
	}

	public OAuthConsumerService getConsumerService() {
		return consumerService;
	}

	public void setConsumerService(OAuthConsumerService consumerService) {
		this.consumerService = consumerService;
	}

	public OAuthTokenService getTokenService() {
		return tokenService;
	}

	public void setTokenService(OAuthTokenService tokenService) {
		this.tokenService = tokenService;
	}

	public OAuthTokenVerifierService getTokenVerifierService() {
		return tokenVerifierService;
	}

	public void setTokenVerifierService(OAuthTokenVerifierService tokenVerifierService) {
		this.tokenVerifierService = tokenVerifierService;
	}

	public OAuthNonceService getNonceService() {
		return nonceService;
	}

	public void setNonceService(OAuthNonceService nonceService) {
		this.nonceService = nonceService;
	}

	public OAuthSignatureFactory getSignatureFactory() {
		return signatureFactory;
	}

	public void setSignatureFactory(OAuthSignatureFactory signatureFactory) {
		this.signatureFactory = signatureFactory;
	}
}
